<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="content-type" content="text/html; charset=UTF-8">
	<meta charset="utf-8">
	<title>WebSocket | 前端开发团队</title>
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 
	<!-- 外联的CSS文件-->
	<link rel="stylesheet" href="../css/bootstrap.css"/> 
	<link rel="stylesheet" href="../css/bootstrap-responsive.css"/> 
	<link rel="stylesheet" href="../css/docs.css"/> 



	<script type="text/javascript" src="../js/SyntaxHighlighter/scripts/XRegExp.js"></script>
	<script type="text/javascript" src="../js/SyntaxHighlighter/scripts/shCore.js"></script>
	<script type="text/javascript" src="../js/SyntaxHighlighter/scripts/shBrushJScript.js"></script>
	<link type="text/css" rel="stylesheet" href="../js/SyntaxHighlighter/styles/shCore.css"/>
	<link type="text/css" rel="Stylesheet" href="../js/SyntaxHighlighter/styles/shThemeDefault.css" />
	<script type="text/javascript">SyntaxHighlighter.all();</script>

	<style>
	body, input, button, textarea {
		font-family: "Microsoft Yahei","微软雅黑",Tahoma,Arial,Helvetica,STHeiti,sans-serif;
	  font-weight: normal;
	}
	div.prettyprint{
		border-radius: 4px 4px 4px 4px;
		display: block;
		font-size: 1em;
		line-height: 20px;
		margin:10px 0px;
	}
	.syntaxhighlighter .container:before, .container:after {
		content: none;
	}
	.syntaxhighlighter .gutter .line {
		 border-right: 0px solid #6CE26C !important
	}
	.syntaxhighlighter,.syntaxhighlighter .line.alt2,.syntaxhighlighter .line.alt1 {
		background: none !important;
	}
	</style> <!--内联 css 代码-->
    <link href="../css/prettify.css" rel="stylesheet">
    <link href="../css/bootstro.css" rel="stylesheet">
</head>

<body>
<!-- Navbar ================================================== -->
<div class="navbar navbar-inverse navbar-fixed-top">
	<div class="navbar-inner">
		<div class="container">
			<a class="brand" href="../index.html">前端开发团队</a>
		
			<div class="nav-collapse collapse">
				<ul class="nav">
					<li class="active">
						<a href="../index.html">首页</a>
					</li>
					<li class="">
						<a class="bootstro" href="../getting-started.html">起步</a>
					</li>
					<li class="">
						<a class="bootstro" href="../scaffolding.html">脚手架</a>
					</li>
					<li class="">
						<a class="bootstro" href="../base-css.html">基本的CSS样式</a>
					</li>
					<li class="">
						<a class="bootstro" href="../components.html">组件</a>
					</li>
					<li class="">
						<a class="bootstro" href="../api.html">API手册</a>
					</li>
					<li class="">
						<a class="bootstro" href="../docs.html">经验交流</a>
					</li>
					<li class="">
						<a class="bootstro" href="../tools.html">工具</a>
					</li>
					<li class="">
						<a class="bootstro" href="../about.html">关于</a>
					</li>
				</ul>
			</div>
		</div>
	</div>
</div>
    

<!-- Subhead
================================================== -->
<header class="jumbotron subhead" id="overview">
  <div class="container">
    <h1>WebSocket</h1>
    <p class="lead">基于 TCP 连接的双通道，构建实时的 web 应用。</p>
  </div>
</header>

  <div class="container">
    <!-- Docs nav
    ================================================== -->
    <div class="row">
      <div class="span3 bs-docs-sidebar">
        <ul class="nav nav-list bs-docs-sidenav affix-top">
          <li><a href="#p1"><i class="icon-chevron-right"></i> 概述</a></li>
		  <li><a href="#p2"><i class="icon-chevron-right"></i> 原理</a></li>
		  <li><a href="#p3"><i class="icon-chevron-right"></i> WebSocket API</a></li>
		  <li><a href="#p4"><i class="icon-chevron-right"></i> 服务端实现</a></li>
		  <li><a href="#pBuild"><i class="icon-chevron-right"></i> 如何搭建WebSockt应用</a></li>
        </ul>
      </div>
      <div class="span9">
			<section id="p1">
				  <div class="page-header">
					<h1>概述</h1>
				  </div>
				  <p>WebSocket的出现使得浏览器提供对 Socket 的支持成为可能，从而在浏览器和服务器之间提供了一个基于 TCP 连接的双向通道。Web 开发人员可以非常方便地使用 WebSocket 构建实时 web 应用。</p>
				  <p>WebSocket通信使用ws或wss协议，服务器和客户端可以彼此相互推送信息，而且允许跨域通信。</p>
				  <p>浏览器兼容情况 ：IE10、Firefox 22、Chrome 28、Opera 16 以及 Safari 6 <a href="http://caniuse.com/#feat=websockets" target="_blank">了解更多</a></p>
			</section>

		
			<section id="p2">
				  <div class="page-header">
					<h1>原理</h1>
				  </div>
				  <p>为了建立一个 WebSocket 连接，客户端浏览器首先要向服务器发起一个 HTTP 请求，这个请求和通常的 HTTP 请求不同，包含了一些附加头信息，其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求，服务器端解析这些附加的头信息然后产生应答信息返回给客户端，客户端和服务器端的 WebSocket 连接就建立起来了，双方就可以通过这个连接通道自由的传递信息，并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。</p>

				<pre class="bs-docs-example">
客户端到服务端： 
GET /demo HTTP/1.1 
Host: example.com 
Connection: Upgrade 
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 
Upgrade: WebSocket 
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5 
Origin: http://example.com 
[8-byte security key] 

服务端到客户端：
HTTP/1.1 101 WebSocket Protocol Handshake 
Upgrade: WebSocket 
Connection: Upgrade 
WebSocket-Origin: http://example.com 
WebSocket-Location: ws://example.com/demo 
[16-byte hash response]</pre>

				<p><code>Upgrade:WebSocket</code> 表示这是一个特殊的 HTTP 请求，请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。从客户端到服务器端请求的信息里包含有<code>Sec-WebSocket-Key1</code>、<code>Sec-WebSocket-Key2</code> 和 <code>[8-byte securitykey]</code> 这是客户端浏览器需要向服务器端提供的握手信息，服务器端解析这些头信息，并在握手的过程中依据这些信息生成一个 16 位的安全密钥并返回给客户端，以表明服务器端获取了客户端的请求，同意创建 WebSocket 连接。一旦连接建立，客户端和服务器端就可以通过这个通道双向传输数据了。</p>

			</section>

			<section id="p3">
				  <div class="page-header">
					<h1>WebSocket API</h1>
				  </div>

				  <p>下面是一段简单的 JavaScript 代码展示了怎样建立 WebSocket 连接和获取数据：</p>
				  <div class="prettyprint linenums">
					  <script type="syntaxhighlighter" class="brush: js; ">
var  wsServer = 'ws://localhost:8888/Demo'; 
//创建Socket实例
var  websocket = new WebSocket(wsServer); 
websocket.onopen = function (evt) { onOpen(evt) }; 
websocket.onclose = function (evt) { onClose(evt) }; 
websocket.onmessage = function (evt) { onMessage(evt) }; 
websocket.onerror = function (evt) { onError(evt) }; 
//打开 Socket
function onOpen(evt) {
	//发送一个初始化消息
	websocket.send('I am the client and I\'m listening!'); 
	console.log("Connected to WebSocket server."); 
	//关闭 socket
	websocket.colse();
}  
//监听关闭
function onClose(evt) { 
	console.log("Disconnected"); 
} 
//监听消息
function onMessage(evt) { 
	console.log('Retrieved data from server: ' + evt.data); 
} 
//监听错误
function onError(evt) { 
	console.log('Error occured: ' + evt.data); 
} 
					   </script>
					  </div>
				 
				  <p>WebSocket API 规范：<a href="http://www.w3.org/TR/websockets/" target="_blank">http://www.w3.org/TR/websockets/</a></p>
			</section>

			<section id="p4">
				  <div class="page-header">
					<h1>服务端实现</h1>
				  </div>
				  <p>在实际的开发过程中，为了使用 WebSocket 接口构建 Web 应用，我们首先需要构建一个实现了 WebSocket 规范的服务，业界目前比较流行是 使用 nodeJS 服务器依靠 <a href="http://socket.io" target="_blank">socket.io</a>来实现，在我们的项目中，我们将使用 Tomcat 7 作为服务器来实现 WebSocket 通信。</p>
				  
				  <p>根据 Tomcat 官方的说法，由于现在的websocket的规范还没有完全制定完成，所以tomcat里面关于websocket的实现的相关API可能随时会改变。具体参考：<a href="http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html" target="_blank">http://tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html</a></p>

				  <p>未完待续...</p>
			</section>

			<section id="pBuild">
				  <div class="page-header">
					<h1>如何搭建WebSockt应用</h1>
				  </div>

				  <p>本段内容以在Tomcat上搭建一个聊天室的应用为例进行说明</p>

				  <section id="pBuildStart">
				  <h2>准备</h2>
				  <p>1、Tomcat：7.0.27以上版本</p>
				  <p>2、JDK：1.6.0以上版本</p>
				  <p>3、Java开发环境：Eclipse</p>
				  </section>

				  <br />
				  <section id="pBuildDevelop">
				  <h2>开发</h2>
				  <p>1、创建Java Web项目</p>

				  <p>2、编写服务端代码</p>
				  <p>（1）聊天室帮助类 ChatHelper</p>
				  <div class="prettyprint linenums">
					  <script type="syntaxhighlighter" class="brush: js; ">
package com.kedacom.webrtc.chat;

import java.util.ArrayList;
import java.util.List;

/**
 * 聊天帮助类
 * @author chengesheng@kedacom.com
 * @date 2013-9-27 下午5:44:54
 * @note ChatHelper.java
 */
public class ChatHelper {

	private static List<ChatClient> clientList = new ArrayList<ChatClient>(0);

	public static List<ChatClient> getClientList() {
		return clientList;
	}
}
					   </script>
				  </div>

				  <br />
				  <p>（2）聊天室客户端类 ChatClient</p>
				  <div class="prettyprint linenums">
					  <script type="syntaxhighlighter" class="brush: js; ">
package com.kedacom.webrtc.chat;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;

/**
 * 聊天室客户端类
 * @author chengesheng@kedacom.com
 * @date 2013-6-27 下午5:34:03
 * @note ChatClient.java
 */
public class ChatClient extends MessageInbound {

	private String clientIp;

	public ChatClient(HttpServletRequest request) {
		this.clientIp = getRemortIp(request);
	}

	public String getClientIp() {
		return clientIp;
	}

	@Override
	protected void onBinaryMessage(ByteBuffer data) throws IOException {
		sendBinaryMessage(data);
	}

	@Override
	protected void onTextMessage(CharBuffer message) throws IOException {
		sendTextMessage("[" + getClientIp() + "] " + message.toString());
	}

	@Override
	protected void onClose(int status) {
		ChatHelper.getClientList().remove(this);
		try {
			sendTextMessage(getClientIp());
		} catch (Exception e) {
			e.printStackTrace();
		}

		super.onClose(status);
	}

	@Override
	protected void onOpen(WsOutbound outbound) {
		super.onOpen(outbound);

		try {
			sendTextMessage(getClientIp());
		} catch (Exception e) {
			e.printStackTrace();
		}
		ChatHelper.getClientList().add(this);
	}

	// 发送文本消息给所有人
	private void sendTextMessage(String message) {
		if (message == null) {
			return;
		}

		for (MessageInbound client : ChatHelper.getClientList()) {
			sendTextMessage(client, message);
		}
	}

	// 发送文本消息给指定的人
	private void sendTextMessage(MessageInbound client, String message) {
		try {
			if (client == null) {
				client = this;
			}

			WsOutbound outbound = client.getWsOutbound();
			outbound.writeTextMessage(string2CharBuffer(message));
			outbound.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 发送字节流给所有人
	private void sendBinaryMessage(ByteBuffer data) {
		if (data == null) {
			return;
		}

		for (MessageInbound client : ChatHelper.getClientList()) {
			sendBinaryMessage(client, data);
		}
	}

	// 发送字节流给指定终端
	private void sendBinaryMessage(MessageInbound client, ByteBuffer data) {
		try {
			if (client == null) {
				client = this;
			}

			WsOutbound outbound = client.getWsOutbound();
			outbound.writeBinaryMessage(data);
			outbound.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 转换String为CharBuffer
	private static CharBuffer string2CharBuffer(String data) {
		return CharBuffer.wrap(data.toCharArray());
	}

	/**
	 * 获取客户端IP地址
	 * @param request
	 * @return
	 */
	private String getRemortIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (isNotIp(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
			if (isNotIp(ip)) {
				ip = request.getHeader("WL-Proxy-Client-IP");
				if (isNotIp(ip)) {
					ip = request.getRemoteAddr();
				}
			}
		}
		return ip;
	}

	/**
	 * 是否是IP地址
	 * @param ip
	 * @return
	 */
	private boolean isNotIp(String ip) {
		return ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) || "127.0.0.1".equals(ip);
	}
}
					   </script>
				  </div>

				  <br />
				  <p>（3）聊天客户端WebSocket类 ChatClientServlet</p>
				  <div class="prettyprint linenums">
					  <script type="syntaxhighlighter" class="brush: js; ">
package com.kedacom.webrtc.chat;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;

/**
 * 聊天客户端WebSocketServlet
 * @author chengesheng@kedacom.com
 * @date 2013-6-27 下午5:44:31
 * @note ChatClientServlet.java
 */
@WebServlet(urlPatterns = "/chat.do")
public class ChatClientServlet extends WebSocketServlet {

	/** serialVersionUID */
	private static final long serialVersionUID = -225065782828683005L;

	/**
	 * @see org.apache.catalina.websocket.WebSocketServlet#createWebSocketInbound(java.lang.String,
	 *      javax.servlet.http.HttpServletRequest)
	 */
	@Override
	protected StreamInbound createWebSocketInbound(String arg0, HttpServletRequest request) {
		return new ChatClient(request);
	}
}
					   </script>
				  </div>

				  <br />			
				  <p>3、编写前台代码 chat.html</p>
				  <div class="prettyprint linenums">
					  <script type="syntaxhighlighter" class="brush: js; ">
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
&lt;title&gt;Index&lt;/title&gt;
&lt;script type="text/javascript"&gt;
var serverUrl = "ws://127.0.0.1/chat.do";
var ws = null;
function startWebSocket() {
	if ('WebSocket' in window)
		ws = new WebSocket(serverUrl);
	else if ('MozWebSocket' in window)
		ws = new MozWebSocket(serverUrl);
	else
		alert("not support");
	
	ws.onmessage = function(evt) {
		showMsg("接收到消息：" + evt.data);
	};
	
	ws.onclose = function(evt) {
		showMsg(evt.data + " 已经下线");
	};
	
	ws.onopen = function(evt) {
		showMsg(evt.data + " 已经上线");
	};
}

function sendMsg() {
	ws.send($('writeMsg').value);
	$('writeMsg').value = "";
}

function showMsg(msg) {
	$('showMsg').value += "\n" + msg;
}

function $(id) {
	return document.getElementById(id);
}
&lt;/script&gt;
&lt;/head&gt;
&lt;body onload="startWebSocket();"&gt;
	打开两个页面模拟聊天室功能&lt;br /&gt;&lt;br /&gt;
	&lt;input type="text" id="writeMsg"&gt;&lt;/input&gt;
	&lt;input type="button" value="send" onclick="sendMsg()"&gt;&lt;/input&gt;
	&lt;textarea id="showMsg" style="width:100%; height: 300px"&gt;&lt;/textarea&gt;
&lt;/body&gt;
&lt;/html&gt;
					   </script>
				  </div>
				  </section>

				  <br />
				  <section id="pBuildDeploy">
				  <h2>部署及运行结果</h2>
				  <p>1、将上面开发好的代码打包发布到Tomcat中</p>
				  <p>2、运行Tomcat</p>
				  <p>3、使用Chrome 25+或Firefox 23+打开两个页面，并分别进入chat.html页面，即可使用两个浏览器页面进行聊天测试，效果如下图所示</p>
				  <p><img src="websocket/images/chat.jpg" width="740" height="569" alt=""></p>
				  <p><a href="http://172.16.8.69:8980/webrtcdemo/websocket.jsp" target="_blank" class="btn btn-large btn-primary">聊天室应用Demo</a></p>
				  </section>
			</section>
      </div>
    </div>

  </div>

    <!-- Footer
    ================================================== -->
    <footer class="footer">
      <div class="container">
        <p>苏州科达科技股份有限公司 - 前端开发团队</p>
      </div>
    </footer>


</body>
</html>